import React, {
  ChangeEvent,
  FC,
  useState,
  ReactElement,
  useEffect,
  KeyboardEvent,
  useRef,
} from 'react'
import classNames from 'classnames'
import {
  AutoCompleteProps,
  DISPLAY_NAME_AUTO_COMPLETE,
  getAutoCompleteClassNames,
  SuggestItemData,
} from './config'
import Input from '../Input'
import Icon from '../Icon'
import Transition from '../Transition'
import useDebounce from '../../hooks/useDebounce'
import useClickOutside from '../../hooks/useClickOutside'

const createDropdown = (
  suggestions: SuggestItemData[],
  handleSelect?: (item: SuggestItemData) => void,
  render: (item: SuggestItemData) => ReactElement | string = item => item.value,
  highlightIndex?: number
) => {
  return suggestions.map((item, index) => {
    const highlightCls = classNames('suggestion-item', {
      'is-active': index === highlightIndex,
    })
    return (
      <li
        key={index}
        className={highlightCls}
        onClick={() => {
          handleSelect && handleSelect(item)
        }}>
        {render(item)}
      </li>
    )
  })
}

export const AutoComplete: FC<AutoCompleteProps> = props => {
  const {
    fetchSuggestions,
    onSelect,
    value,
    renderOption,
    ...restProps
  } = props
  const autoCompleteClasses = getAutoCompleteClassNames(props)

  const [inputValue, setInputValue] = useState(value as string)
  const debounceInputValue = useDebounce(inputValue)
  const [suggestions, setSuggestions] = useState<SuggestItemData[]>([])
  const [showDropdown, setShowDropdown] = useState(false)
  const [highlightIndex, setHighlightIndex] = useState(-1)
  const [loading, setLoading] = useState(false)

  const handleSelect = (item: SuggestItemData) => {
    setInputValue(item.value)
    setSuggestions([])
    onSelect && onSelect(item)
    triggerSearch.current = false
  }
  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value.trim()
    setInputValue(value)
    triggerSearch.current = true
  }
  const handleInputChick = () => {
    setShowDropdown(true)
  }
  const handleHighlight = (index: number) => {
    if (index < 0) index = suggestions.length - 1
    index = index % suggestions.length
    setHighlightIndex(index)
  }
  const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
    setShowDropdown(true)
    switch (e.code) {
      case 'Enter':
        if (suggestions[highlightIndex]) {
          handleSelect(suggestions[highlightIndex])
        }
        break
      case 'ArrowUp':
        handleHighlight(highlightIndex - 1)
        break
      case 'ArrowDown':
        handleHighlight(highlightIndex + 1)
        break
      case 'Escape':
        setShowDropdown(false)
        break
    }
  }

  const triggerSearch = useRef(false)
  const componentRef = useRef<HTMLDivElement>(null)
  useEffect(() => {
    if (!debounceInputValue || triggerSearch.current === false) {
      setShowDropdown(false)
      return
    }
    setShowDropdown(true)
    const results = fetchSuggestions(debounceInputValue)
    if (results instanceof Promise) {
      setLoading(true)
      setSuggestions([])
      results
        .then(data => {
          setSuggestions(data)
          setLoading(false)
        })
        .catch(e => {
          setLoading(false)
          return Promise.reject(e)
        })
    } else {
      setSuggestions(results)
    }
    setHighlightIndex(-1)
  }, [debounceInputValue, fetchSuggestions])

  useClickOutside(componentRef, () => {
    setShowDropdown(false)
  })

  return (
    <div className={autoCompleteClasses} ref={componentRef}>
      <Input
        value={inputValue}
        {...restProps}
        onChange={handleChange}
        onKeyDown={handleKeyDown}
        onClick={handleInputChick}></Input>

      <Transition
        in={(showDropdown && suggestions && suggestions.length > 0) || loading}
        animation="zoom-in-top"
        timeout={300}>
        <ul className="suggestion-list">
          {loading && (
            <div className="suggestion-loading-icon">
              <Icon icon="spinner" spin></Icon>
            </div>
          )}
          {suggestions &&
            suggestions.length > 0 &&
            createDropdown(
              suggestions,
              handleSelect,
              renderOption,
              highlightIndex
            )}
        </ul>
      </Transition>
    </div>
  )
}
AutoComplete.displayName = DISPLAY_NAME_AUTO_COMPLETE

export default AutoComplete
